package com.heima.schedule.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.common.constants.ScheduleConstants;
import com.heima.common.redis.CacheService;
import com.heima.model.schedule.dtos.Task;
import com.heima.model.schedule.pojos.Taskinfo;
import com.heima.model.schedule.pojos.TaskinfoLogs;
import com.heima.schedule.mapper.TaskinfoLogsMapper;
import com.heima.schedule.mapper.TaskinfoMapper;
import com.heima.schedule.service.TaskService;
import com.sun.jmx.snmp.tasks.TaskServer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * @author lzq
 * @data 2023/5/5 11:27
 */
@Service
@Transactional
@Slf4j
public class TaskServiceImpl implements TaskService {
    @Autowired
    private TaskinfoMapper taskinfoMapper;
    @Autowired
    private TaskinfoLogsMapper taskinfoLogsMapper;
    @Autowired
    private CacheService cacheService;

    /**
     * 添加任务
     * @param task   任务对象
     * @return
     */
    @Override
    public long addTask(Task task) {
        //1保存任务到数据库
        Boolean flag=addTaskDb(task);
        //2 保存任务到redis
        if (flag){
            addTaskRedis(task);
        }
        return task.getTaskId();
    }

    /**
     * 取消任务
     * @param taskId        任务id
     * @return
     */
    @Override
    public boolean cancelTask(long taskId) {
        boolean flag=false;
        //删除数据库任务,修改日志
        Task task = deleteToDb(taskId, ScheduleConstants.CANCELLED);
        //删除redis中的任务数据
        if (task!=null){
            deleteToRedis(task);
            flag=true;
        }
        return flag;
    }

    /**
     * 拉取任务
     * @param type
     * @param priority
     * @return
     */
    @Override
    public Task poll(int type, int priority) {
        Task task = null;
        try {
            //拼接key
            String key=type+"_"+priority;
            //拉去除list中最右边的数据,并删除list中数据
            String json_task = cacheService.lRightPop(ScheduleConstants.TOPIC + key);
            if (StringUtils.isNotBlank(json_task)){
                 task = JSON.parseObject(json_task, Task.class);
                //消费成功.删除任务表数据,修改日志
                deleteToDb(task.getTaskId(),ScheduleConstants.EXECUTED);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("poll task exception");
        }
        return task;
    }

    /**
     * 删除redis中的任务数据
     */
    private void deleteToRedis(Task task) {
        String key=task.getTaskType()+"_"+task.getPriority();
        //删除list中的取消数据
        if (task.getExecuteTime()<=System.currentTimeMillis()){
            cacheService.lRemove(ScheduleConstants.TOPIC+key,0,JSON.toJSONString(task));
        }else {
            cacheService.zRemove(ScheduleConstants.FUTURE+key,JSON.toJSONString(task));
        }
    }

    /**
     * 删除数据库任务,修改日志
     * @param taskId
     * @param status
     */
    private Task deleteToDb(long taskId, int status) {
        //删除任务表
        taskinfoMapper.deleteById(taskId);
        //修改日志表
        TaskinfoLogs taskinfoLogs = taskinfoLogsMapper.selectById(taskId);
        taskinfoLogs.setStatus(status);
        taskinfoLogsMapper.updateById(taskinfoLogs);
        //返回task
        Task task = new Task();
        BeanUtils.copyProperties(taskinfoLogs,task);
        task.setExecuteTime(taskinfoLogs.getExecuteTime().getTime());
        return task;
    }

    /**
     * 保存到redis
     * @param task
     */
    private void addTaskRedis(Task task) {
        //设置key由类型和优先级
        String key=task.getTaskType()+"_"+task.getPriority();
        //获取5分钟之后的时间  毫秒值
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, 5);
        long nextScheduleTime = calendar.getTimeInMillis();

        //2.1 如果任务执行时间<当前时间,保存到list
        if (task.getExecuteTime()<=System.currentTimeMillis()){
            //引用工具类作为标识为立刻执行数据TOPIC   未来执行数据FUTURE
            cacheService.lLeftPush(ScheduleConstants.TOPIC+key, JSON.toJSONString(task));
        }else if (task.getExecuteTime()<=nextScheduleTime){
            //2.2 当前时间<如果任务执行时间<预设时间(当前时间+提前预设的时间量 [5分钟]),存入zset
            cacheService.zAdd(ScheduleConstants.FUTURE+key,JSON.toJSONString(task),task.getExecuteTime());
        }
    }

    /**
     * 保存到数据库
     * @param task
     */
    private Boolean addTaskDb(Task task) {
        Boolean flag=false;
        try {
            //1创建任务对象数据拷贝
            Taskinfo taskinfo = new Taskinfo();
            BeanUtils.copyProperties(task,taskinfo);
            //不匹配属性处理
            taskinfo.setExecuteTime(new Date(task.getExecuteTime()));
            //储存任务表
            taskinfoMapper.insert(taskinfo);
            //返回taskId  从任务表中获取
            task.setTaskId(taskinfo.getTaskId());

            //2创建任务日志对象
            TaskinfoLogs taskinfoLogs = new TaskinfoLogs();
            //属性拷贝
            BeanUtils.copyProperties(taskinfo,taskinfoLogs);
            //缺少字段补充
            taskinfoLogs.setVersion(1);
            //初始化状态 ScheduleConstants.SCHEDULED (0)
            taskinfoLogs.setStatus(ScheduleConstants.SCHEDULED);
            //保存到日志表
            taskinfoLogsMapper.insert(taskinfoLogs);
            flag=true;
        } catch (BeansException e) {
            e.printStackTrace();
        }
        return flag;
    }

    /**
     * 1.刷新方法,将zset中的数据每一分钟一次刷新到list中
     * 2.刷新数据,如果部署redis集群,会导致数据重复问题,所以使用分布式锁,保证每次只有一个微服务可以刷新
     */
    @Scheduled(cron = "0 */1 * * * ?") //定时刷新功能,cron表达式,每一分钟刷新一次
    public void refresh(){
        //name:key名称. expire:锁的过期释放时间
        String tryLock = cacheService.tryLock("FUTURE_TASK_SYNC", 1000 * 30);
        //如果获得了所才能执行
        if (StringUtils.isNotBlank(tryLock)){
            //通过scan方法,获取zset中的所有key
            Set<String> futureKeys = cacheService.scan(ScheduleConstants.FUTURE + "*");
            //遍历
            for (String futureKey : futureKeys) {
                //截取得到key中type和Priority属性
                String[] split = futureKey.split(ScheduleConstants.FUTURE);
                //这是储存到list的key
                String topicKey= ScheduleConstants.TOPIC+split[1];
                //获取当前时间范围futureKey对应的所有数据
                Set<String> tasks = cacheService.zRangeByScore(futureKey, 0, System.currentTimeMillis());
                if (!tasks.isEmpty()){
                    //数据转移
                    cacheService.refreshWithPipeline(futureKey,topicKey,tasks);
                    log.info("将"+futureKey+"转移到"+topicKey+"中成功");
                }
            }
        }
    }

    /**
     * 定义数据库同步任务到redis方法 ,五分钟刷新一次
     */
    @Scheduled(cron = "0 */5 * * * ?")
    @PostConstruct   //初始化之后自启动此方法(springBean的生命周期)1:实例化对象 2:DI 给对象属性赋值
    //3: 初始化 4:工作 5:销毁
    public void reloadData(){
        //1删除redis缓存
        clearRedis();
        //2将数据库符合条件的放入redis
        //获取5分钟之后的时间  毫秒值
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, 5);
        //获取数据库所有时间小于预定时间的任务
        List<Taskinfo> taskinfoList = taskinfoMapper.selectList(Wrappers.<Taskinfo>lambdaQuery().lt(Taskinfo::getExecuteTime, calendar.getTime()));
       if (taskinfoList!=null&&taskinfoList.size()>0){
           for (Taskinfo taskinfo : taskinfoList) {
               Task task = new Task();
               BeanUtils.copyProperties(taskinfo,task);
               task.setExecuteTime(taskinfo.getExecuteTime().getTime());
               //存入redis
               addTaskRedis(task);
           }
       }
    }
    //清理redis缓存
    private void clearRedis(){
        Set<String> topicKyes= cacheService.scan(ScheduleConstants.TOPIC + "*");
        Set<String> futureKyes= cacheService.scan(ScheduleConstants.FUTURE + "*");
        cacheService.delete(futureKyes);
        cacheService.delete(topicKyes);
    }
}
